#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/World.h>
#include <Kharlia/Engine/Level.h>
#include <Kharlia/Engine/Actor.h>
#include <Kharlia/Engine/Engine.h>
#include <Kharlia/Engine/Player.h>
#include <Kharlia/Core/ObjectUtil.h>
#include <Kharlia/Core/App.h>
#include <Kharlia/Core/Tasker.h>

namespace kharlia { namespace engine {

using namespace py;

// Max allowed length for function names sent with RPC
static const uint8 RPC_FUNCNAME_MAX_LEN = 64;
// Max allowed length for pickled argument tuple
static const uint16 RPC_ARGSTR_MAX_LEN = 0xFFFF;
// Length of buffer allocated on stack to try to read argument string into.
// If the argument string is longer than this, memory will be allocated.
static const uint16 RPC_ARGSTR_STACK_BUFFER_LEN = 255;

//bool WriteGUID(RN::BitStream* out, object& guid) {
//    long_ guidint = extract<long_>(guid.attr("int"));
//    unsigned char bytes[16];
//    if (_PyLong_AsByteArray(reinterpret_cast<PyLongObject*>(guidint.ptr()), bytes, 16, false, false) < 0) {
//        return false;
//    }
//    out->Write(bytes, 16);
//    return true;
//}
//
//bool ReadGUID(RN::BitStream* in, object& outguid, object constructor) {
//    unsigned char bytes[16];
//    in->Read(bytes, 16);
//    long_ guidint
//}

// cPickle.dumps
static object GRPCPickleFunc;
// cPickle.HIGHEST_PROTOCOL
static object GRPCPickleProtocol;
// cPickle.loads
static object GRPCUnpickleFunc;

//
// FRMConnection and FReplicaManager
//

rn::Replica3* FRMConnection::AllocReplica(rn::BitStream* input, rn::ReplicaManager3* rmx) {
    //const uint32 BUF_SIZE = 64;
    //sint32 levelNameHash;
    //char classNameBuf[BUF_SIZE];
    //char actorNameBuf[BUF_SIZE];
    //bool hasOwner = false;

    //FReplicaManager* rm = dynamic_cast<FReplicaManager*>(rmx);
    //assert(rm);
    //assert(rm->mWorld);

    //input->Read(levelNameHash);
    //object levelNameHashO(levelNameHash);
    //assert(PyInt_CheckExact(levelNameHashO.ptr()));

    //input->Read(classNameBuf);
    //str className(classNameBuf);

    //input->Read(actorNameBuf);
    //unicode actorName(actorNameBuf);

    //input->Read(hasOwner);

    //shared_ptr<CLevel> level = rm->mWorld->GetLevel(levelNameHashO);
    //object cls = ResolveName(className);
    //if (hasOwner) {
    //    sint32 ownerNameHash;
    //    input->Read(ownerNameHash);
    //    object ownerNameHashO(ownerNameHash);
    //    assert(PyInt_CheckExact(ownerNameHashO.ptr()));

    //    shared_ptr<AActor> owner = level->GetActor(ownerNameHashO);
    //    assert(owner);
    //    object actorO = level->CreateActor(cls, owner, actorName);
    //    AActor* actor = extract<AActor*>(actorO);
    //    return static_cast<rn::Replica3*>(actor);
    //}
    //else {
    //    object actorO = level->CreateActor(cls, shared_ptr<AActor>(), actorName);
    //    AActor* actor = extract<AActor*>(actorO);
    //    return static_cast<rn::Replica3*>(actor);
    //}
    return NULL;
}

rn::Connection_RM3* FReplicaManager::AllocConnection(const rn::SystemAddress& addr, rn::RakNetGUID guid) const {
    //shared_ptr<CPlayer> player = ConstructObjectEx<CPlayer>("kharlia.engine.CPlayer", false);
    //rn::Connection_RM3* conn = new FRMConnection(addr, guid, player);
    //player->SetAddress(addr);
    //player->SetGUID(guid);
    //player->SetRMConnection(conn);
    //list(mWorld->GetEngine()->GetPlayers()).append(player);
    //return conn;
    return new FRMConnection(addr, guid);
}

void FReplicaManager::DeallocConnection(rn::Connection_RM3* connb) const {
    FRMConnection* conn = dynamic_cast<FRMConnection*>(connb);
    //assert(conn);
    //list players = mWorld->GetEngine()->GetPlayers();
    //object playero, next = GetIterNextFunc(players);
    //int i = 0;
    //while (Iterate(next, playero)) {
    //    shared_ptr<CPlayer> player = extract<shared_ptr<CPlayer>>(playero);
    //    if (player->GetGUID() == conn->GetRakNetGUID()) {
    //        assert(player->GetRMConnection() == conn && conn->GetPlayer().get() == player.get());
    //        player->SetGUID(rn::RakNetGUID());
    //        player->SetAddress(rn::SystemAddress());
    //        player->SetRMConnection(NULL);
    //        players[i].del();
    //        break;
    //    }
    //    i++;
    //}
    delete conn;
}

void FReplicaManager::UpdateNow() {
    using namespace rn;

    // Code taken from ReplicaManager3::Update()

    DataStructures::DefaultIndexType index,index2;
    for (index=0; index < connectionList.GetSize(); index++) {
        if (connectionList[index]->isValidated==false)
            continue;
        connectionList[index]->AutoConstructByQuery(this);
    }
    for (index=0; index < userReplicaList.GetSize(); index++) {
        userReplicaList[index]->forceSendUntilNextUpdate=false;
        userReplicaList[index]->OnUserReplicaPreSerializeTick();
    }

    rn::Time time = rn::GetTimeMS();

    SerializeParameters sp;
    sp.curTime=time;
    FRMConnection *connection;
    SendSerializeIfChangedResult ssicr;
    sp.messageTimestamp=0;
    for (int i=0; i < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; i++)
        sp.pro[i]=defaultSendParameters;
    index2=0;
    for (index=0; index < connectionList.GetSize(); index++)
    {
        connection = static_cast<FRMConnection*>(connectionList[index]);
        sp.bitsWrittenSoFar=0;
        index2=0;
        while (index2 < connection->queryToSerializeReplicaList.GetSize())
        {
            sp.destinationConnection=connection;
            sp.whenLastSerialized=connection->queryToSerializeReplicaList[index2]->replica->whenLastSerialized;
            ssicr=connection->SendSerializeIfChanged(index2, &sp, GetRakPeerInterface(), GetWorldID(), this);
            if (ssicr==SSICR_SENT_DATA)
            {
                connection->queryToSerializeReplicaList[index2]->replica->whenLastSerialized=time;
                index2++;
            }
            else if (ssicr==SSICR_NEVER_SERIALIZE)
            {
                // Removed from the middle of the list
            }
            else
                index2++;
        }
    }
}

// -------------------------------------------------
// CGame
// -------------------------------------------------

CGame::CGame(PyObject* self):
    CObject(self),
    mWorld(),
    mPaused(false),
    mStarted(false)
{
}

void CGame::_OnStartGame(shared_ptr<CWorld> world) {
    assert(!mStarted);
    mStarted = true;
    mWorld = world;
}

void CGame::_OnStopGame() {
    assert(mStarted);
    mWorld.reset();
    mStarted = false;
}

void CGame::Tick(float deltaTime) {
}

KH_BEGIN_GC(CGame)
    KH_GC_SHARED_PTR(thisx->mWorld)
KH_END_GC()

//
// CWorld
//

object CWorld::msCreateGUIDFunc;
object CWorld::msGUIDClass;
object CWorld::msLevelClass;

CWorld::CWorld(PyObject* self):
    CObject(self),
    mTicking(false),
    mInited(false),
    mEngine(),
    // Network
    mNetPeer(NULL),
    mNetIsEnabled(false),
    mNetIsServer(false),
    mNetIsClient(false),
    mNetIsListening(false),
    mNetIsConnecting(false),
    mNetClientConnections(),
    mNetServerConnection(),
    mNetReplicaManager(NULL),
    mNetTickRate(0),
    mNetRepRate(0),
    mNetTickElapsed(0.0),
    mNetRepElapsed(0.0)
{
}

CWorld::~CWorld() {
    assert(!mInited);
}

void CWorld::Init(shared_ptr<CEngine> eng) {
    if (mInited)
        return;
    mEngine = eng;
    assert(mEngine);
    mLogger = mEngine->GetLogger();
    CreateDefaultLevel();
    mInited = true;
}

void CWorld::Exit() {
    if (!mInited)
        return;
    StopGame();
    DestroyLevels();
    NetExit();
    mEngine.reset();
    mInited = false;
}

void CWorld::Tick(float deltaTime) {
    bool received = false;
    mTicking = true;
    // Receive packets
    if (mNetPeer) {
        mNetTickElapsed += deltaTime;
        if (mNetTickRate == 0 || mNetTickElapsed >= (1.0f / float(mNetTickRate))) {
            NetTickReceive(mNetTickElapsed);
            received = true;
        }
    }
    // Tick levels and their actor tasklets
    if (mGame)
        mGame->Tick(deltaTime);
    LevelPtrsType::iterator it;
    for (it = mLevelPtrs.begin(); it != mLevelPtrs.end(); it++) {
        (*it)->Tick(deltaTime);
    }
    // Send processed information
    if (received) {
        NetTickSend(mNetTickElapsed);
        mNetTickElapsed = 0.0;
    }
    mTicking = false;
}

void CWorld::NetTickSend(float deltaTime) {
    // Send processed information
    if (mNetPeer && mNetReplicaManager && mNetReplicaManager->GetWorldPtr()) {
        mNetRepElapsed += deltaTime;
        if (mNetRepRate == 0 || mNetRepElapsed >= (1.0f / float(mNetRepRate))) {
            mNetReplicaManager->UpdateNow();
            mNetRepElapsed = 0.0;
        }
    }
}

shared_ptr<CLevel> CWorld::CreateLevel(object guid, unicode name, const FSizeI& size) {
    if (mLevels.contains(name))
        throw RuntimeError("name already in use");
    if (guid.is_none())
        guid = CreateGUID();
    if (!PyObject_IsInstance(guid.ptr(), msGUIDClass.ptr()))
        throw RuntimeError("specified guid must be instance of uuid.UUID");
    if (msLevelClass.is_none())
        throw RuntimeError("level class not specified");

    // We can't create a level while we're ticking or our iterator will explode
    if (mTicking) {
        if (core::IsCurrentTaskletMain()) {
            throw RuntimeError("attempted to create level during tick");
        }
        mLogger->Warning("creating level during world tick");
        while (mTicking) {
            core::CApp::GetInstancePtr()->GetTasker()->Yield(0);
        }
    }

    object level = msLevelClass();
    shared_ptr<CLevel> levelptr = extract<shared_ptr<CLevel>>(level);

    try {
        levelptr->Init(GetSelfPtr<CWorld>(), guid, name, size);

        mLevels[name] = level;
        mLevelHashMap[levelptr->GetNameHash()] = level;
        mLevelPtrs.push_back(levelptr.get());
        
        levelptr->OnCreated();

        if (GetGameStarted())
            levelptr->OnGameStarted(mGame);

        return levelptr;
    }
    catch (...) {
        if (levelptr)
            levelptr->Exit();
        if (mLevels.contains(name))
            mLevels[name].del();
        if (levelptr) {
            if (mLevelHashMap.contains(levelptr->GetNameHash()))
                mLevelHashMap[levelptr->GetNameHash()].del();
            if (mLevelPtrs.size() > 0 &&
                mLevelPtrs[mLevelPtrs.size() - 1] == levelptr.get())
                mLevelPtrs.pop_back();
        }
        throw;
    }
}

shared_ptr<CLevel> CWorld::CreateDefaultLevel() {
    if (!mDefaultLevel)
        mDefaultLevel = CreateLevel(object(), unicode(L"__Default__"), FSizeI(1, 1));
    return mDefaultLevel;
}

void CWorld::DestroyLevel(shared_ptr<CLevel> level) {
    if (level->GetWorld().get() != this)
        throw RuntimeError("not holding this level");

    if (mTicking)
        throw RuntimeError("currently ticking levels");

    if (GetGameStarted())
        level->OnGameStopped();

    level->OnDestroyed();

    level->Exit();

    mLevelPtrs.erase(std::find(mLevelPtrs.begin(), mLevelPtrs.end(), level.get()));
    mLevels[level->GetName()].del();
    mLevelHashMap[level->GetNameHash()].del();

    if (mDefaultLevel == level)
        mDefaultLevel.reset();
}

void CWorld::DestroyLevels() {
    using namespace py;

    list levels;
    object level, nextfunc;

    nextfunc = core::GetIterNextFunc(mLevels.itervalues());
    while (core::Iterate(nextfunc, level)) {
        levels.append(level);
    }

    nextfunc = core::GetIterNextFunc(levels);
    while (core::Iterate(nextfunc, level)) {
        DestroyLevel(extract<shared_ptr<CLevel>>(level));
    }
}

void CWorld::StartGame() {
    if (mGame && mGame->GetStarted())
        return;

    if (mGameClass.is_none())
        throw RuntimeError("game class unspecified");

    if (mLevelPtrs.size() == 0)
        throw RuntimeError("no levels exist");

    if (mNetIsClient)
        // Can't construct game on the client
        return;

    assert(!mNetIsEnabled || mNetIsServer);

    if (!mGame)
        mGame = ConstructObjectEx<CGame>(mGameClass);
    
    mGame->OnStartGame(GetSelfPtr<CWorld>());
    for (uint i = 0; i < mLevelPtrs.size(); i++) {
        mLevelPtrs[i]->OnGameStarted(mGame);
    }
}

void CWorld::StopGame() {
    if (!mGame || !mGame->GetStarted())
        return;
    
    mGame->OnStopGame();
    for (uint i = 0; i < mLevelPtrs.size(); i++) {
        mLevelPtrs[i]->OnGameStopped();
    }
    mGame.reset();
}

ENetMode CWorld::GetNetMode() {
    if (mNetIsEnabled) {
        if (mNetIsClient) {
            return NM_CLIENT;
        }
        else {
            if (mEngine->GetIsClient()) {
                return NM_LISTEN_SERVER;
            }
            else {
                return NM_DEDICATED_SERVER;
            }
        }
    }
    else {
        return NM_STANDALONE;
    }
    throw RuntimeError("Couldn't get net mode, unknown state");
}

shared_ptr<CLevel> CWorld::GetLevel(object name) {
    object level;
    if (PyUnicode_CheckExact(name.ptr())) {
        level = mLevels.get(name, level);
    }
    else if (PyInt_CheckExact(name.ptr())) {
        level = mLevelHashMap.get(name, level);
    }
    else {
        throw TypeError("level name must be unicode or int object");
    }
    if (level.is_none())
        return shared_ptr<CLevel>();
    return extract<shared_ptr<CLevel>>(level);
}

//shared_ptr<CActor> CWorld::GetActor(object levelName, object actorName) {
//    shared_ptr<CLevel> level = GetLevel(levelName);
//    if (!level)
//        return shared_ptr<CActor>();
//    return level->GetActor(actorName);
//}

void CWorld::SetLevelClass(object cls) {
    object lvlcls = GetTypeObject(type_id<CLevel>());
    assert(!lvlcls.is_none());
    if (!PyObject_IsSubclass(cls.ptr(), lvlcls.ptr()))
        throw TypeError("level class must be subtype of kharlia._engine.CLevel");
    msLevelClass = cls;
}

void CWorld::NetListen() {
    if (mNetPeer)
        throw RuntimeError("already listening or connecting");
    
    mNetPeer = rn::RakPeerInterface::GetInstance();
    rn::StartupResult result = mNetPeer->Startup(5, &rn::SocketDescriptor(60500, NULL), 1);

    switch (result) {
    case rn::RAKNET_STARTED:
        break;
    default:
        throw RuntimeError("RakNet failed to startup");
    }

    assert(!mNetReplicaManager);
    mNetReplicaManager = new FReplicaManager(this);
    mNetReplicaManager->SetAutoSerializeInterval(0);
    mNetReplicaManager->SetDefaultOrderingChannel(5);
    mNetReplicaManager->SetDefaultPacketPriority(MEDIUM_PRIORITY);
    mNetReplicaManager->SetDefaultPacketReliability(RELIABLE_SEQUENCED);
    mNetReplicaManager->SetNetworkIDManager(&mNetIDManager);
    mNetPeer->AttachPlugin(mNetReplicaManager);

    mNetPeer->SetOccasionalPing(true);
#ifdef _DEBUG
    mNetPeer->SetTimeoutTime(300000, rn::UNASSIGNED_SYSTEM_ADDRESS);
#endif
    mNetPeer->SetMaximumIncomingConnections(4);

    mNetIsEnabled = true;
    mNetIsServer = true;
    mNetIsListening = true;
}

void CWorld::NetConnect(str host, uint16 port) {
    if (mNetPeer)
        throw RuntimeError("already listening or connecting");

    mNetPeer = rn::RakPeerInterface::GetInstance();
    rn::StartupResult result = mNetPeer->Startup(1, &rn::SocketDescriptor(0, NULL), 1);

    switch (result) {
    case rn::RAKNET_STARTED:
        break;
    default:
        throw RuntimeError("RakNet failed to startup");
    }

    assert(!mNetReplicaManager);
    mNetReplicaManager = new FReplicaManager(this);
    mNetReplicaManager->SetAutoSerializeInterval(0);
    mNetReplicaManager->SetDefaultOrderingChannel(5);
    mNetReplicaManager->SetDefaultPacketPriority(MEDIUM_PRIORITY);
    mNetReplicaManager->SetDefaultPacketReliability(RELIABLE_SEQUENCED);
    mNetReplicaManager->SetNetworkIDManager(&mNetIDManager);
    mNetPeer->AttachPlugin(mNetReplicaManager);

    mNetPeer->SetOccasionalPing(true);
#ifdef _DEBUG
    mNetPeer->SetTimeoutTime(300000, rn::UNASSIGNED_SYSTEM_ADDRESS);
#endif

    rn::ConnectionAttemptResult cresult = mNetPeer->Connect(extract<const char*>(host), port, NULL, 0);
    if (cresult != rn::CONNECTION_ATTEMPT_STARTED)
        throw RuntimeError("connection attempt failed");

    mNetIsEnabled = true;
    mNetIsConnecting = true;
}

void CWorld::NetExit() {
    assert(mNetIsEnabled == (bool)mNetPeer);
    if (!mNetIsEnabled)
        return;

    mNetServerConnection = rn::RakNetGUID();
    mNetClientConnections.clear();

    assert(mNetReplicaManager);
    mNetPeer->DetachPlugin(mNetReplicaManager);
    delete mNetReplicaManager;
    mNetReplicaManager = NULL;

    mNetPeer->Shutdown(2000);
    rn::RakPeerInterface::DestroyInstance(mNetPeer);
    mNetPeer = NULL;

    mNetIsEnabled = false;
    mNetIsClient = false;
    mNetIsServer = false;
    mNetIsConnecting = false;
    mNetIsListening = false;
}

void CWorld::NetTickReceive(float deltaTime) {
    if (!mNetPeer)
        throw RuntimeError("network interface not active");
    
    assert(sizeof(rn::MessageID) == sizeof(uint8));

    rn::Packet* p;
    uint8 pid;
    bool dealloc;
    while ((p = mNetPeer->Receive()) != NULL) {
        pid = GetPacketIdentifier(p);
        dealloc = true;

        switch (pid) {
        // RakNet messages
        case ID_NEW_INCOMING_CONNECTION:
            if (mNetIsListening) {
                char addr[32];
                p->systemAddress.ToString(true, addr);
                format msg("New incomming connection: %1%");
                msg % addr;
                mLogger->Info(msg);
                //mNetClientConnections.push_back(p->guid);
                OnClientConnect(p->guid, p->systemAddress);
            }
            else {
                mLogger->Info("New connection dismissed");
            }
            break;

        case ID_CONNECTION_REQUEST_ACCEPTED:
            {
                assert(mNetIsConnecting);
                mEngine->OnNetClientConnected(GetSelfPtr<CWorld>());
                mNetIsClient = true;
                mNetIsConnecting = false;
                mNetServerConnection = p->guid;
                char addr[32];
                p->systemAddress.ToString(true, addr);
                format msg("Connection request accepted: %1%");
                msg % addr;
                mLogger->Info(msg);
            }
            break;

        // Engine Messages
        case ID_DEBUG_MESSAGE:
            {
                char msg[DEBUG_MESSAGE_MAX_SIZE];
                rn::BitStream input(p->data, p->length, false);
                input.IgnoreBytes(sizeof(rn::MessageID));
                rn::StringCompressor::Instance()->DecodeString(
                    msg, DEBUG_MESSAGE_MAX_SIZE, &input);
                mLogger->Debug(str("Net debug message: ") + str(msg));
            }
            break;

        case ID_ACTOR_RPC:
            {
                mLogger->Debug("received actor RPC packet");
                rn::BitStream input(p->data, p->length, false);
                IgnorePacketIdentifier(&input);
                NetOnReceiveActorRPC(&input);
            }
            break;
        }

        if (dealloc)
            mNetPeer->DeallocatePacket(p);
    }
}

void CWorld::OnClientConnect(rn::RakNetGUID guid, rn::SystemAddress addr) {
    // This is called when a client connections to a server
    assert(mNetIsServer);
    mNetClientConnections.push_back(guid);
    // Create their player
    //shared_ptr<CPlayer> p = ConstructObjectEx<CPlayer>("kharlia.engine.CPlayer", false);
    //p->SetGUID(guid);
    //p->SetAddress(addr);
    //mPlayers.append(p);
}

void CWorld::NetSendDebugMessage(str msg) {
    assert(mNetPeer);
    rn::BitStream output;

    output.Write((rn::MessageID)ID_DEBUG_MESSAGE);
    rn::StringCompressor::Instance()->EncodeString(
        extract<char*>(msg), DEBUG_MESSAGE_MAX_SIZE, &output);

    mNetPeer->Send(&output, HIGH_PRIORITY, RELIABLE_ORDERED, 0, mNetServerConnection, true);
}

void CWorld::NetSendActorRPC(bool reliable, shared_ptr<CActor> actor, str funcname, tuple args) {
    // We're not supporting specific destinations yet for this test, so
    // make sure we have something to send to in case of server->client
    //if (!mNetPeer ||
    //    (GetNetIsClient() && mNetServerConnection == rn::UNASSIGNED_RAKNET_GUID) ||
    //    (!GetNetIsClient() && mNetClientConnections.size() < 1)) {
    //    throw RuntimeError("cannot send actor RPC, destination unavailable");
    //}

    //// Destination should already exist, so use hashes for names where possible
    //uint8 funcNameLen = len(funcname);
    //sint32 nameHash = actor->GetNameHashAsInt();
    //sint32 levelHash = actor->GetLevel()->GetNameHashAsInt();
    //uint8 arity = len(args);
    //object argStr;
    //uint16 argStrLen = 0;

    //if (funcNameLen > RPC_FUNCNAME_MAX_LEN)
    //    throw RuntimeError("function name too long");
    //else if (funcNameLen < 1)
    //    throw RuntimeError("function name is empty");

    //if (arity > 0) {
    //    argStr = GRPCPickleFunc(args, GRPCPickleProtocol);
    //    assert(PyString_CheckExact(argStr.ptr()));
    //    argStrLen = PyString_GET_SIZE(argStr.ptr());
    //    if (argStrLen > RPC_ARGSTR_MAX_LEN)
    //        throw RuntimeError("pickled argument string too large");
    //}
    //
    //// Create our packet:
    //// id:1, levelhash:4, namehash:4, funcname:1x, args:2x
    //rn::BitStream out;
    //out.Write((rn::MessageID)ID_ACTOR_RPC);
    //out.Write(levelHash);
    //out.Write(nameHash);

    //out.Write(funcNameLen);
    //out.WriteAlignedBytes((uchar*)PyString_AS_STRING(funcname.ptr()), funcNameLen);

    //if (arity > 0) {
    //    out.Write(argStrLen);
    //    out.WriteAlignedBytes((uchar*)PyString_AS_STRING(argStr.ptr()), argStrLen);
    //}
    //
    //PacketReliability rel = reliable? RELIABLE: UNRELIABLE;
    //// For now, only send to first client for testing purposes
    //rn::RakNetGUID dest = GetNetIsClient()? mNetServerConnection: mNetClientConnections[0];
    //uint32 result = mNetPeer->Send(&out, MEDIUM_PRIORITY, rel, 0, dest, false);
    //if (result == 0)
    //    throw RuntimeError("bad input for RakPeer::Send");
}

void CWorld::NetOnReceiveActorRPC(rn::BitStream* in) {
    //sint32 hashTemp;
    //char funcNameBuf[RPC_FUNCNAME_MAX_LEN],
    //     argsStrBuf[RPC_ARGSTR_STACK_BUFFER_LEN];
    //uint8 funcNameLen;
    //uint16 argStrLen;
    //object levelHash, actorHash, funcName, argStr;
    //tuple args;

    //in->Read(hashTemp);
    //levelHash = object(hashTemp);
    //in->Read(hashTemp);
    //actorHash = object(hashTemp);

    //in->Read(funcNameLen);
    //if (funcNameLen > RPC_FUNCNAME_MAX_LEN)
    //    throw RuntimeError("function name too long on received actor RPC");
    //assert(funcNameLen > 0);
    //in->ReadAlignedBytes((uchar*)funcNameBuf, funcNameLen);
    //funcName = str(funcNameBuf, funcNameLen);

    //in->Read(argStrLen);
    //if (argStrLen > 0) {
    //    if (argStrLen > RPC_ARGSTR_MAX_LEN)
    //        throw RuntimeError("rpc args str is too long");
    //    // Read string to stack if possible
    //    if (argStrLen <= RPC_ARGSTR_STACK_BUFFER_LEN) {
    //        in->ReadAlignedBytes((uchar*)argsStrBuf, argStrLen);
    //        argStr = str(argsStrBuf, argStrLen);
    //    }
    //    else {
    //        scoped_array<char> buf(new char[argStrLen]);
    //        in->ReadAlignedBytes((uchar*)buf.get(), argStrLen);
    //        argStr = str(buf.get(), argStrLen);
    //    }
    //    args = extract<tuple>(GRPCUnpickleFunc(argStr));
    //}

    //shared_ptr<CActor> actor = GetActor(levelHash, actorHash);
    //if (!actor)
    //    throw RuntimeError("target actor not found");
    //
    //// Tasklet out the call
    //const shared_ptr<core::CTasker>& atm = actor->GetLevel()->GetActorTasker();
    //object meth = actor->GetSelfAttr(funcName);
    //object tasklet = atm->CreateTasklet(meth);
    //handle<>(PyObject_Call(tasklet.ptr(), args.ptr(), NULL));
    //atm->InsertTasklet(tasklet, CLevel::PRE_TICK_GROUP);
}

object CWorld::CreateGUID() {
    return msCreateGUIDFunc();
}

KH_BEGIN_GC(CWorld)
    KH_GC_SHARED_PTR(thisx->mEngine)
    KH_GC_SHARED_PTR(thisx->mGame)
    KH_GC_OBJECT_EX(thisx->mLevels, dict)
KH_END_GC()

void _InitWorldClasses() {
    _InitWorldBindings();

    object uuid = import("uuid");
    object cpickle = import("cPickle");

    CWorld::msCreateGUIDFunc = uuid.attr("uuid4");
    CWorld::msGUIDClass = uuid.attr("UUID");

    GRPCPickleFunc = cpickle.attr("dumps");
    GRPCPickleProtocol = cpickle.attr("HIGHEST_PROTOCOL");
    GRPCUnpickleFunc = cpickle.attr("loads");
}

void _ExitWorldClasses() {
    CWorld::msCreateGUIDFunc = object();
    CWorld::msGUIDClass = object();
    CWorld::msLevelClass = object();
    GRPCPickleFunc = object();
    GRPCPickleProtocol = object();
    GRPCUnpickleFunc = object();
}

}}